#include <iostream>
#include <mpi.h>

#include "Timer.h"
#include "MPI_TopoCart2D.h"
#include "PWaveData3D.h"
#include "PWaveProblem.h"

#include "swt_env_win_linux.h"
#include "lct_env_macros.h"


#ifdef _SWT_WIN32_
#include "io.h"
#else
#include <unistd.h>
#endif


#ifndef _SWT_WIN32_

#include "sys/sysinfo.h"
#include "sys/types.h"


void show_mem_usage() {
    struct sysinfo memInfo;
    sysinfo(&memInfo);
    long long totalPhysMem = memInfo.totalram;    // Multiply in next statement to avoid int overflow on right hand side...
    totalPhysMem *= memInfo.mem_unit;
    double total_physical_memory_in_GB = totalPhysMem * 1.0 / 1024 / 1024 / 1024;
    long long free_ram = memInfo.freeram;    // Multiply in next statement to avoid int overflow on right hand side...
    free_ram *= memInfo.mem_unit;
    double total_free_ram_used_in_GB = free_ram * 1.0 / 1024 / 1024 / 1024;
    long long physMemUsed = memInfo.totalram - memInfo.freeram;    // Multiply in next statement to avoid int overflow on right hand side...
    physMemUsed *= memInfo.mem_unit;
    double total_physical_memory_used_in_GB = physMemUsed * 1.0 / 1024 / 1024 / 1024;
    std::cout << "total physical memory:      " << total_physical_memory_in_GB << " GB." << std::endl;
    std::cout << "total physical memory used: " << total_physical_memory_used_in_GB << " GB." << std::endl;
    std::cout << "total free ram:             " << total_free_ram_used_in_GB << " GB." << std::endl;
}

#endif


int main(int argc, char* argv[]) {
    int comm_siz;                           // num of processes
    int proc_id;                            // id of this process
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &proc_id);

    CmdLineArgs cmd(argc, argv);

#ifdef USE_GNU_COMPILER
    cmd.check_and_make_subdir();    // it is discovered in experiments that errno seems to be incompatible with intel compilers
#endif // USE_GNU_COMPILER

    PWaveProblem problem;
    PWaveData3D data;
    MPI_TopoCart2D topo;

    problem.init(&cmd);

    problem.partition(&data);
#ifndef _SWT_WIN32_
    show_mem_usage();
#endif
    data.allocate();


    if(proc_id == 0) {
        problem.write_log(&data, 0, 0, 0, 0);
        printf("create log file ok.\n");
    }

    problem.create_MPI_topo(&topo);


    double start_abs_sec;
    //! barrier before timing
    MPI_Barrier(MPI_COMM_WORLD);
    if(proc_id == 0)
        start_abs_sec = Timer::tik();


    problem.init_model(&data, &topo);
#ifdef __DEBUG__
    std::cout << "finish init model\n";
#endif
    //! barrier after initializing whole model values
    MPI_Barrier(MPI_COMM_WORLD);
    double init_abs_sec;
    if(proc_id == 0)
        init_abs_sec = Timer::tik();



    double* dst = problem.solve(&data, &topo);



    //! barrier after computation
    MPI_Barrier(MPI_COMM_WORLD);
    double compute_abs_sec;
    if(proc_id == 0)
        compute_abs_sec = Timer::tik();



    //! barrier after outputting
    double output_abs_sec;
    if(proc_id == 0)
        output_abs_sec = Timer::tik();


    if(proc_id == 0) {
        std::cout << "time_init =\t" << init_abs_sec - start_abs_sec << " s\ntime_compute =\t"
                  << compute_abs_sec - init_abs_sec << " s\ntime_output_final_3D =\t"
                  << output_abs_sec - compute_abs_sec << " s\n";

        problem.write_log(&data, init_abs_sec, compute_abs_sec, output_abs_sec, start_abs_sec);
    }

    data.free();
    problem.free();

    MPI_Finalize();
    return 0;
}
